package org.bouncycastle.bcpg;

import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;

import org.bouncycastle.bcpg.sig.EmbeddedSignature;
import org.bouncycastle.bcpg.sig.Exportable;
import org.bouncycastle.bcpg.sig.Features;
import org.bouncycastle.bcpg.sig.IntendedRecipientFingerprint;
import org.bouncycastle.bcpg.sig.IssuerFingerprint;
import org.bouncycastle.bcpg.sig.IssuerKeyID;
import org.bouncycastle.bcpg.sig.KeyExpirationTime;
import org.bouncycastle.bcpg.sig.KeyFlags;
import org.bouncycastle.bcpg.sig.LibrePGPPreferredEncryptionModes;
import org.bouncycastle.bcpg.sig.NotationData;
import org.bouncycastle.bcpg.sig.PolicyURI;
import org.bouncycastle.bcpg.sig.PreferredAEADCiphersuites;
import org.bouncycastle.bcpg.sig.PreferredAlgorithms;
import org.bouncycastle.bcpg.sig.PreferredKeyServer;
import org.bouncycastle.bcpg.sig.PrimaryUserID;
import org.bouncycastle.bcpg.sig.RegularExpression;
import org.bouncycastle.bcpg.sig.Revocable;
import org.bouncycastle.bcpg.sig.RevocationKey;
import org.bouncycastle.bcpg.sig.RevocationReason;
import org.bouncycastle.bcpg.sig.SignatureCreationTime;
import org.bouncycastle.bcpg.sig.SignatureExpirationTime;
import org.bouncycastle.bcpg.sig.SignatureTarget;
import org.bouncycastle.bcpg.sig.SignerUserID;
import org.bouncycastle.bcpg.sig.TrustSignature;
import org.bouncycastle.util.Arrays;
import org.bouncycastle.util.io.Streams;

/**
 * reader for signature sub-packets
 */
public class SignatureSubpacketInputStream
    extends InputStream
    implements SignatureSubpacketTags
{
    private final InputStream in;
    private final int limit;

    public SignatureSubpacketInputStream(
        InputStream in)
    {
        this(in, StreamUtil.findLimit(in));
    }

    public SignatureSubpacketInputStream(
        InputStream in,
        int limit)
    {
        this.in = in;
        this.limit = limit;
    }

    public int available()
        throws IOException
    {
        return in.available();
    }

    public int read()
        throws IOException
    {
        return in.read();
    }

    public SignatureSubpacket readPacket()
        throws IOException
    {
        boolean[] flags = new boolean[3];
        int bodyLen = StreamUtil.readBodyLen(this, flags);
        if (flags[StreamUtil.flag_eof])
        {
            return null;
        }
        else if (flags[StreamUtil.flag_partial])
        {
            throw new IOException("unexpected length header");
        }
        boolean isLongLength = flags[StreamUtil.flag_isLongLength];

        int tag = in.read();

        if (tag < 0)
        {
            throw new EOFException("unexpected EOF reading signature sub packet");
        }

        // see below about miscoding... we'll try not to panic about anything under 2K.
        if (bodyLen <= 0 || (bodyLen > limit && bodyLen > 2048))
        {
            throw new EOFException("out of range data found in signature sub packet");
        }

        byte[] data = new byte[bodyLen - 1];

        //
        // this may seem a bit strange but it turns out some applications miscode the length
        // in fixed length fields, so we check the length we do get, only throwing an exception if
        // we really cannot continue
        //
        int bytesRead = Streams.readFully(in, data);

        boolean isCritical = ((tag & 0x80) != 0);
        int type = tag & 0x7f;

        if (bytesRead != data.length)
        {
            switch (type)
            {
            case CREATION_TIME:
                data = checkData(data, 4, bytesRead, "Signature Creation Time");
                break;
            case ISSUER_KEY_ID:
                data = checkData(data, 8, bytesRead, "Issuer");
                break;
            case KEY_EXPIRE_TIME:
                data = checkData(data, 4, bytesRead, "Signature Key Expiration Time");
                break;
            case EXPIRE_TIME:
                data = checkData(data, 4, bytesRead, "Signature Expiration Time");
                break;
            default:
                throw new EOFException("truncated subpacket data.");
            }
        }

        try
        {
            switch (type)
            {
            case CREATION_TIME:
                return new SignatureCreationTime(isCritical, isLongLength, data);
            case EMBEDDED_SIGNATURE:
                return new EmbeddedSignature(isCritical, isLongLength, data);
            case KEY_EXPIRE_TIME:
                return new KeyExpirationTime(isCritical, isLongLength, data);
            case EXPIRE_TIME:
                return new SignatureExpirationTime(isCritical, isLongLength, data);
            case REVOCABLE:
                return new Revocable(isCritical, isLongLength, data);
            case EXPORTABLE:
                return new Exportable(isCritical, isLongLength, data);
            case FEATURES:
                return new Features(isCritical, isLongLength, data);
            case ISSUER_KEY_ID:
                return new IssuerKeyID(isCritical, isLongLength, data);
            case TRUST_SIG:
                return new TrustSignature(isCritical, isLongLength, data);
            case PREFERRED_COMP_ALGS:
            case PREFERRED_HASH_ALGS:
            case PREFERRED_SYM_ALGS:
                return new PreferredAlgorithms(type, isCritical, isLongLength, data);
            case LIBREPGP_PREFERRED_ENCRYPTION_MODES:
                return new LibrePGPPreferredEncryptionModes(isCritical, isLongLength, data);
            case PREFERRED_AEAD_ALGORITHMS:
                return new PreferredAEADCiphersuites(isCritical, isLongLength, data);
            case PREFERRED_KEY_SERV:
                return new PreferredKeyServer(isCritical, isLongLength, data);
            case KEY_FLAGS:
                return new KeyFlags(isCritical, isLongLength, data);
            case POLICY_URL:
                return new PolicyURI(isCritical, isLongLength, data);
            case PRIMARY_USER_ID:
                return new PrimaryUserID(isCritical, isLongLength, data);
            case SIGNER_USER_ID:
                return new SignerUserID(isCritical, isLongLength, data);
            case NOTATION_DATA:
                return new NotationData(isCritical, isLongLength, data);
            case REG_EXP:
                return new RegularExpression(isCritical, isLongLength, data);
            case REVOCATION_REASON:
                return new RevocationReason(isCritical, isLongLength, data);
            case REVOCATION_KEY:
                return new RevocationKey(isCritical, isLongLength, data);
            case SIGNATURE_TARGET:
                return new SignatureTarget(isCritical, isLongLength, data);
            case ISSUER_FINGERPRINT:
                return new IssuerFingerprint(isCritical, isLongLength, data);
            case INTENDED_RECIPIENT_FINGERPRINT:
                return new IntendedRecipientFingerprint(isCritical, isLongLength, data);
            }
        }
        catch (IllegalArgumentException e)
        {
            throw new MalformedPacketException("Malformed signature subpacket.", e);
        }

        return new SignatureSubpacket(type, isCritical, isLongLength, data);
    }

    private byte[] checkData(byte[] data, int expected, int bytesRead, String name)
        throws EOFException
    {
        if (bytesRead != expected)
        {
            throw new EOFException("truncated " + name + " subpacket data.");
        }

        return Arrays.copyOfRange(data, 0, expected);
    }
}
